import fg from 'fast-glob';
import fs from 'fs-extra';
import yaml from 'yaml';
import _ from 'lodash';
import path from 'path';
import { Unity } from '../typings/unity.js';
import { toolchain } from '../toolchain.js';

export declare interface ITextureMeta {
    guid: string;
    textureType: number;
    spriteBorder: { x: number, y: number, z: number, w: number };
}

export class UnityHelper {
    public static readonly CompositeFileTypes = ['.anim', '.controller', '.fontsettings', '.mat', '.overrideController', '.physicsMaterial2D', '.prefab', '.spriteatlas', '.terrainlayer', '.unity', '.asset', '.bytes'];
    public static async collectUsedGUID(dir: string, exclude?: string[]): Promise<Record<string, string[]>> {
        // 收集用到的guid
        const out: Record<string, string[]> = {};
        const pattern = UnityHelper.CompositeFileTypes.filter((v) => exclude == null || !exclude.includes(v)).map((v) => `**/*${v}`);
        const prefabs = await fg(pattern, { cwd: dir });
        for (const prefab of prefabs) {
            // if (prefab.endsWith('10160030.prefab')) {
            //     console.log('aaa');
            // }
            const file = path.join(dir, prefab);
            const content = await fs.readFile(file, 'utf-8');
            const guids = content.matchAll(/guid: (\w{32})/g);
            for (const g of guids) {
                const guid = g[1];
                let arr = out[guid];
                if (arr == null) {
                    out[guid] = arr = [];
                }
                if (!arr.includes(file)) arr.push(file);
            }
        }
        return out;
    }
    
    public static async collectExistingGUID(dir: string): Promise<string[]> {
        // 收集所有存在的guid
        const out: string[] = [];
        const metas = await fg(['**/*.meta'], { cwd: dir });
        for (const meta of metas) {
            const file = path.join(dir, meta);
            const guid = await this.readGUID(file);
            if (out.includes(guid)) {
                console.warn('guid conflicts:', guid);
            } else {
                out.push(guid);
            }
        }
        return _.uniq(out);
    }

    public static async getScriptGUID(scriptName: string): Promise<string | null> {
        const r = path.join(toolchain.opts.projectRoot, 'Assets');
        const scripts = await fg(`**/${scriptName}`, { cwd: r });
        if (scripts.length > 0) {
            return await UnityHelper.readGUID(path.join(r, scripts[0]) + '.meta');
        }
        return null;
    }

    public static async readGUID(metaFile: string): Promise<string> {
        const content = await fs.readFile(metaFile, 'utf-8');
        const result = content.match(/guid: (\w{32})/);
        if (result == null) {
            console.error('Cannot parse guid from:', metaFile);
            return '';
        }
        return result[1];
    }

    public static async readTextureMeta(metaFile: string): Promise<ITextureMeta> {
        const content = await fs.readFile(metaFile, 'utf-8');
        const guidRst = content.match(/guid: (\w{32})/);
        const guid = guidRst != null ? guidRst[1] : '';
        const textureTypeRst = content.match(/textureType: (\d+)/);
        const textureType = textureTypeRst != null ? Number(textureTypeRst[1]) : 0;
        const spriteBorderRst = content.match(/spriteBorder: \{x: (\d*\.?\d+), y: (\d*\.?\d+), z: (\d*\.?\d+), w: (\d*\.?\d+)\}/)!;
        const spriteBorder = { x: Number(spriteBorderRst[1]), y: Number(spriteBorderRst[2]), z: Number(spriteBorderRst[3]), w: Number(spriteBorderRst[4]) };
        return { guid, textureType, spriteBorder };
    }

    public static async loadMeta<T extends Unity.Meta.MetaTypes>(metaFile: string): Promise<T> {
        const content = await fs.readFile(metaFile, 'utf-8');
        return yaml.parse(content);
    }

    public static async unlinkFile(file: string): Promise<void> {
        await fs.unlink(file);
        await fs.unlink(file + '.meta');
    }

    public static async removeDir(dir: string): Promise<void> {
        await fs.remove(dir);
        if (fs.existsSync(dir + '.meta')) await fs.unlink(dir + '.meta');
    }
}
